import { stringUtils } from 'my-ts-core';
import { RouteMapManager } from 'my-ts-core/security';

import { validateToken } from './jwt';
import { getCachedRequestMaps, type RequestMapQuery } from './request-map-manager';
import { ParsedToken } from './types';

export interface UserActiveTokenManager {
  isActiveAccessToken(
    username: string,
    deviceId: string | null,
    accessToken: string,
  ): Promise<boolean>;
}

export async function checkAuthorization(
  requestMethod: string,
  requestUri: string,
  authorizationHeader: string,
  userActiveTokenManager: UserActiveTokenManager,
  requestMapQuery: RequestMapQuery,
): Promise<boolean> {
  let granted = false;

  const urlFullPath = requestUri.trim();

  if (!stringUtils.isNullOrUndefinedOrEmptyString(urlFullPath)) {
    // Gets authorization from request header.
    // The string should be like below example:
    // Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDU3ODIxNzUsImlhdCI6MTYwNTc3ODU3NSwiaWQiOiJ1c2VyLWlkIiwidXNlcm5hbWUiOiJIdWkiLCJyb2xlcyI6WyJBZG1pbiJdfQ.tKdhYNyeqOS_ITIV_F0sKT6N-NBFjxMkPyXAkzT43WQ

    // Validates token string to see if it is a valid access token, and it does not expire yet.
    const parsedToken = checkAuthorizationHeader(authorizationHeader);

    // If token is valid, we still need to check if it is still active.
    if (parsedToken.valid) {
      parsedToken.valid = await userActiveTokenManager.isActiveAccessToken(
        parsedToken.claims!.username,
        parsedToken.claims!.deviceId ?? null,
        parsedToken.token,
      );
    }

    // If user was authenticated but token is no longer valid or user is not granted for current URL, 401 code will be sent.
    if (parsedToken.claims && !parsedToken.valid) {
      granted = false;
    } else {
      const cachedRequestMaps = await getCachedRequestMaps(requestMapQuery);

      // If token is valid we need to check on URL access permission.
      granted = RouteMapManager.checkCanActivateRoute(
        cachedRequestMaps,
        urlFullPath,
        parsedToken.claims?.roles,
        requestMethod,
      );
    }
  }

  return granted;
}

export function checkAuthorizationHeader(authorizationHeader: string | null): ParsedToken {
  let result = new ParsedToken();

  if (!stringUtils.isNullOrUndefinedOrEmptyString(authorizationHeader)) {
    // Splits the string authorizationHeader around each instance of one or more consecutive white space characters.
    const fields = authorizationHeader!.trim().split(/\s+/);

    // Checks if the string contains bearer and token.
    if (fields.length === 2 && fields[0]?.toLowerCase() === 'bearer') {
      // Gets token string.
      const token = fields[1]!;

      // Validates token string to see if it is a valid access token, and it does not expire yet.
      result = validateToken(token);
    }
  }

  return result;
}
